Skip to content

Document structured concurrency #4433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: develop
Choose a base branch
from

Conversation

dkhalanskyjb
Copy link
Collaborator

@dkhalanskyjb dkhalanskyjb commented May 9, 2025

Draft for now, because the documentation of some other functions should be updated accordingly:

  • The remaining entries in CoroutineScope.kt.
  • supervisorScope
  • Builders.common.kt: launch, async, and the other coroutine builders should document their structured concurrency behaviors.
  • The withTimeout family of functions.
  • produce.
  • Coroutine builders from the reactive integrations.
  • JS+Wasm promise.
  • The future function.

but reviewing the changes should already be okay (I'll introduce further changes in separate commits).

cc @LouisCAD, given your real-world experience and interest in the documentation, your comments on this would be greatly appreciated!

@dkhalanskyjb dkhalanskyjb requested a review from qwwdfsad May 9, 2025 12:08
@dkhalanskyjb dkhalanskyjb force-pushed the dkhalanskyjb/fix-structured-concurrency-docs branch from ea4a81c to 4ad4272 Compare May 21, 2025 09:13
@dkhalanskyjb dkhalanskyjb requested a review from murfel July 9, 2025 11:11
Copy link
Contributor

@murfel murfel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if it's too nitpicky. Or if you'd prefer the first round to be more high-level, and then you'd be interested in nitpicky to polish it up. Your feedback to my feedback is appreciated 🙂

* thus enforcing the structured concurrency. See [Job] documentation for more details.
* The methods of this interface are not intended to be called directly.
* Instead, a [CoroutineScope] is passed as a receiver to the coroutine builders such as [launch] and [async],
* controlling the execution properties and lifetimes of the created coroutines.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lifetimes -> lifecycles

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate?

* The [CoroutineScope.cancel] extension function shall be used when the entity that was launching coroutines
* is no longer needed. It cancels all the coroutines that might still be running on behalf of it.
* Manual implementations of this interface are not recommended.
* Instead, there are several structured ways to obtain a [CoroutineScope].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

structured

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

* Some commonly used dispatchers are provided in the [Dispatchers] object.
* - [CoroutineExceptionHandler] that defines how failures of child coroutines should be reported whenever
* structured concurrency does not provide a way to propagate the failure to the parent
* (typically, because the root scope of the ancestry tree is not lexically scoped).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi: this is where I had to lookup what "lexically scoped" means, my understanding from the google summary is "where / in which context it is defined / written in the source code"

but this still leaves me wondering what are the specific cases this "not lexically scoped" refers to

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I managed to get the gist of "lexically" after reading this section, but I stumbled every time I saw this word.

It only gets clear after the Lexical scopes and CoroutineScope constructor sections.

Possibly define it, avoid using before these sections, or refer to those sections?

Comment on lines +60 to +62
* [coroutineScope] and [supervisorScope] functions can be used in any `suspend` function to define a scope
* lexically, ensuring that all coroutines launched in this scope have completed by the time scope-limiting
* function exits.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split the sentence in two to simplify reading. Less ideas per sentence.

Suggested change
* [coroutineScope] and [supervisorScope] functions can be used in any `suspend` function to define a scope
* lexically, ensuring that all coroutines launched in this scope have completed by the time scope-limiting
* function exits.
* [coroutineScope] and [supervisorScope] functions can be used in any `suspend` function to define a scope
* lexically. All coroutines launched in the new scope will have completed by the time the scope-limiting
* function exits.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's worth avoiding the new term "scope-limiting function". Maybe "by the time the lexical scope function exits"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less ideas per sentence is good, but is it really okay to have less than one idea per sentence? In my view, it's better to have a longer sentence that describes a single idea instead of having two sentences with 0.5 ideas each.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new term "scope-limiting function". Maybe "by the time the lexical scope function exits"?

"The lexical scope function" is also a new term, though. What makes a function lexical-scope? Fully stated, coroutineScope is a function that limits the coroutine scope to the lexical scope of its block, yet this is a mouthful and distracts from the idea that's being conveyed. Do you have an idea of how this could be expressed clearly but also succinctly?

* } // will only exit once all `Coroutine X finished` messages are printed
* ```
*
* This should be the preferred way to create a scope for coroutines.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move it before the example? So that there's no confusion if it refers to this section, or if it's a setup for the next section.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it confusing in the rendered form as well? It's expected that this text will be read from kotlinlang.org or from the IDE in the popup window that appears on a mouse-over.

*
* This should be the preferred way to create a scope for coroutines.
*
* ### `CoroutineScope` constructor function
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it just be called constructor, instead of constructor function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoroutineScope is not a class and does not have a constructor, we only provide a constructor function.

Comment on lines +165 to +167
* It is convenient for launching top-level coroutines that are not tied to the lifecycle of any entity,
* but it is easy to misuse it and create memory leaks or resource leaks when a coroutine actually should be tied
* to the lifecycle of some entity.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify by splitting into two, less ideas per sentence.

However, it is easy to misuse...

(Let me know if you'd like me to point out other occurrences of this in my future reading bouts of this PR.)

(By the way, prompting an LLM with "rewrite this sentence" gives out the same sentence split that what I would suggest, and some stylistically better words that I wouldn't come up with myself. Although it needs some editing to put all the erased terms back in.)

Comment on lines +169 to +178
* ```
* GlobalScope.launch(CoroutineExceptionHandler { _, e ->
* println("Error in coroutine: $e")
* }) {
* while (true) {
* println("I will be running forever, you cannot stop me!")
* delay(1.seconds)
* }
* }
* ```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it's more of an example, unrelated to GlobalScope, for "if you define your custom exception handler which ignores exceptions, no one will be able to cancel you", rather than "if you misuse GlobalScope, you're setting yourself up for failure".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it relies on the hidden curriculum of "cancelation is implemented as an exception" which I believe hasn't been covered up to here yet.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you define your custom exception handler which ignores exceptions, no one will be able to cancel you

That's not true, though. The CoroutineExceptionHandler is here just to solidify the good practice of supplying them. Removing the CoroutineExceptionHandler does not change the point.

Comment on lines +182 to +183
* When all else fails and a custom [CoroutineScope] implementation is needed, it is recommended to use
* `by`-delegation to implement the interface:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fails is a very specific word and brings unneeded connotations.

When no other approaches are applicable

* }
* ```
*
* ### `by`-delegation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprised that I only learn that I should use by-delegration instead of the constructor [function] at the very end of the section. Possibly mention in the beginning that the preferred approach is the by-delegation, and the constructor is only spelled out there for clarity/demonstration?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example is using the constructor function here to obtain a non-custom implementation. The constructor function is better than by-delegation, which is in turn better than override val coroutineContext: CoroutineContext.

@dkhalanskyjb
Copy link
Collaborator Author

Higher-level overview first is nice, since there's no point in debating the grammar of a section that gets removed as a whole. So, the nitpicking should (initially) be limited to exactly the level above which everything's already well done.

* Some commonly used dispatchers are provided in the [Dispatchers] object.
* - [CoroutineExceptionHandler] that defines how failures of child coroutines should be reported whenever
* structured concurrency does not provide a way to propagate the failure to the parent
* (typically, because the root scope of the ancestry tree is not lexically scoped).
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a specific example make it clear?

Suggested change
* (typically, because the root scope of the ancestry tree is not lexically scoped).
* (typically, because the root scope of the ancestry tree is not lexically scoped,
that is, not created using builders like [coroutineScope] or [withContext]
that return the result directly to the caller).

Comment on lines +165 to +167
* It is convenient for launching top-level coroutines that are not tied to the lifecycle of any entity,
* but it is easy to misuse it and create memory leaks or resource leaks when a coroutine actually should be tied
* to the lifecycle of some entity.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splitting removes the focus on the contradiction, which is the whole point. Would this work instead?

Suggested change
* It is convenient for launching top-level coroutines that are not tied to the lifecycle of any entity,
* but it is easy to misuse it and create memory leaks or resource leaks when a coroutine actually should be tied
* to the lifecycle of some entity.
* Although is convenient for launching top-level coroutines that are not tied to the lifecycle of any entity,
* it is easy to misuse it and create memory leaks or resource leaks when a coroutine actually should be tied
* to the lifecycle of some entity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants